打靶(递归算法) | 八皇后(回溯算法)

面试例题1一个射击运动员打靶,靶一共有10环,连开10枪打中90环的可能性有多少种?请用递归算法编程实现。[中国某著名通信企业H面试题]

解析:靶上一共有10种可能——1环到10环,还有可能脱靶,那就是0环,加在一起共11种可能。这是一道考循环和递归的面试题。我们在这个程序中将利用递归的办法实现打靶所有可能的演示,并计算出结果。读者会问,难道一定要使用递归?当然不是。我们也可以连续用10个循环语句来表示程序,代码如下:

for (i1=0;i1<=10;i1++)

 {

      for (i2=0;i2<=10;i2++)

      {

          for (i3=0;i3<=10;i3++)

          {

              ......

                for (i10=0;i10<=10;i10++)

                  {

                      if(i1+i2+i3+...+i10=90)

                      Print();

                 }

              ......

          }

      }

 }

但是,上面的循环程序虽然解决了问题,但时间复杂度和空间复杂度无疑是很高的。比较好的办法当然是采用递归的方式,事实上公司也就是这么设计的。递归的条件由以下4步完成:

1)如果出现这种情况,即便后面每枪都打10环也无法打够总环数90,在这种情况下就不用再打了,则退出递归。代码如下:

if(score < 0 || score > (num+1)*10 ) //次数num09

         {

              return;

         }

2)如果满足条件且打到最后一次(因为必须打10次),代码如下:

    if(num == 0) 

     {

         store2[num] = score;

         Output( store2);

         return;

        

     }

3)如果没有出现以上两种情况则执行递归,代码如下:

    for(int i = 0; i <= 10; ++i)

        {

            //这里实际上为了方便把顺序倒了过来,store2[9]是第1

            //store2[8]是第2回……store2[0]是第10

            store2[num] = i;

            Cumput(score - i, num - 1,store2);

        }

4)打印函数,符合要求的则把它打印出来。代码如下:

    public static void Output(int[] store2)

    {

       

        for(int i = 9; i>=0; --i)

        {

            Console.Write("   {0}",store2[i]);

           

        }

        Console.WriteLine();

        sum++;

               

    }

答案:

C#编写的完整代码如下:

using System ;

 

public class M

{

 

    //public static int[] store;

    //相当于设置了全局变量

    //这个全局变量sum是包含在M类中的

    public static int sum;

    public M()

    {

        int sum =0;

        // int[] store = {1,2,3,4,5,6,7,8,9,0};

       

    }

   

//打印函数

    //符合要求的则把它打印出来

    public static void Output(int[] store2)

    {

       

        for(int i = 9; i>=0; --i)

        {

            Console.Write("   {0}",store2[i]);

           

        }

        Console.WriteLine();

        sum++;

               

    }

   

    //计算总数,返回sum

    public static int sum2()

    {

        return sum;

    }

 

    public static void Cumput(int score, int num, int[] store2 )

    {

       

        //如果总的成绩超过了90环(也就是score<0),或者如果剩下要打靶

        //的成绩大于10环乘以剩下要打的次数,也就是说即便后面的都打10

        //也无法打够次数,则退出递归

        if(score < 0 || score > (num+1)*10 ) //次数num09

        {

            return;

        }

       

        //如果满足条件且达到最后一层

        if(num == 0) 

        {

            store2[num] = score;

            Output( store2);

            return;

           

        }

       

        for(int i = 0; i <= 10; ++i)

        {

            store2[num] = i;

            Cumput(score - i, num - 1,store2);

        }

        //Console.Write("   {0}",store2[5]);

    }

}

 

public class myApp

{

    public static void Main( )

    {

        int[] store;

        store = new int[10];

        int sum = 0;

        //int a=90;

        //int b=9;

        //Output();

        M.Cumput(90,9,store);

        sum = M.sum2();

       

        //M.Cumput2(a,b,store);

        //Console.Write("   {0}",store[3]);

        //cout<<"总数:"<<sum<<endl;

        Console.Write(" 总数:   {0}",sum);

       

    }

}  

程序结果一共有92 378种可能。

也可以用C++编写,代码如下:

#include <iostream>

using namespace std;

int sum;

int store[10];

void Output()

{

    for(int i = 9; i>=0; --i)

    {

       cout<<store[i]<<" ";

    }

   cout<<endl;

    ++sum;

}

 

void Cumput(int score, int num)

{

   if(score < 0 || score > (num+1)*10 ) //次数num09

      return;

   if(num == 0) 

     {

        store[num] = score;

        Output();

        return;

     }

   for(int i = 0; i <= 10; ++i)

     {

        store[num] = i;

        Cumput(score - i, num - 1);

     }

}

 

int main(int argc, char* argv[])

    {

       Cumput(90, 9);

       cout<<"总数:"<<sum<<endl;

       return 0;

    }

面试例题2八皇后问题是一个古老而著名的问题,是回溯算法的典型例题。该问题是19世纪著名的数学家高斯1850年提出:在8×8格的国际象棋盘上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。[英国某著名计算机图形图像公司面试题]

解析:递归实现n皇后问题。

算法分析:

数组abc分别用来标记冲突,a数组代表列冲突,从a[0]~a[7]代表第0列到第7列。如果某列上已经有皇后,则为1,否则为0

数组b代表主对角线冲突,为b[i-j+7],即从b[0]~b[14]。如果某条主对角线上已经有皇后,则为1,否则为0

数组c代表从对角线冲突,为c[i+j],即从c[0]~c[14]。如果某条从对角线上已经有皇后,则为1,否则为0

代码如下:

#include <stdio.h>

 

static char Queen[8][8];

static int a[8];

static int b[15];

static int c[15];

static int iQueenNum=0; //记录总的棋盘状态数

 

void qu(int i);     //参数i代表行

 

int main()

{

 int iLine,iColumn;

 

 //棋盘初始化,空格为*,放置皇后的地方为@

 for(iLine=0;iLine<8;iLine++)

 {

    a[iLine]=0; //列标记初始化,表示无列冲突

    for(iColumn=0;iColumn<8;iColumn++)

      Queen[iLine][iColumn]='*';

 }

 

 //主、从对角线标记初始化,表示没有冲突

 for(iLine=0;iLine<15;iLine++)

    b[iLine]=c[iLine]=0;

 

 qu(0);

 return 0;

}

 

void qu(int i)

{

 int iColumn;

 

 for(iColumn=0;iColumn<8;iColumn++)

 {

    if(a[iColumn]==0&&b[i-iColumn+7]==0&&c[i+iColumn]==0)

    //如果无冲突

    {

      Queen[i][iColumn]='@'; //放皇后

      a[iColumn]=1;           //标记,下一次该列上不能放皇后

      b[i-iColumn+7]=1;       //标记,下一次该主对角线上不能放皇后

      c[i+iColumn]=1;             //标记,下一次该从对角线上不能放皇后

      if(i<7) qu(i+1);        //如果行还没有遍历完,进入下一行

      else //否则输出

      {

        //输出棋盘状态

        int iLine,iColumn;

        printf("%d种状态为:\n",++iQueenNum);

        for(iLine=0;iLine<8;iLine++)

        {

          for(iColumn=0;iColumn<8;iColumn++)

            printf("%c ",Queen[iLine][iColumn]);

          printf("\n");

        }

        printf("\n\n");

      }

 

      //如果前次的皇后放置导致后面的放置无论如何都不能满足要求,则回溯,重置

      Queen[i][iColumn]='*';

      a[iColumn]=0;

      b[i-iColumn+7]=0;

      c[i+iColumn]=0;

    }

 }

}

posted @ 2007-07-05 15:16  大牛博客  阅读(3055)  评论(0编辑  收藏  举报